/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef itkSimplexMeshToTriangleMeshFilter_hxx
#define itkSimplexMeshToTriangleMeshFilter_hxx


namespace itk
{

template <typename TInputMesh, typename TOutputMesh>
void
SimplexMeshToTriangleMeshFilter<TInputMesh, TOutputMesh>::GenerateData()
{
  this->Initialize();
  this->CreateTriangles();
}

template <typename TInputMesh, typename TOutputMesh>
void
SimplexMeshToTriangleMeshFilter<TInputMesh, TOutputMesh>::Initialize()
{
  const SimplexVisitorInterfacePointer simplexVisitor = SimplexVisitorInterfaceType::New();

  simplexVisitor->SetMesh(this->GetInput(0));
  const CellMultiVisitorPointer mv = CellMultiVisitorType::New();
  mv->AddVisitor(simplexVisitor);
  this->GetInput(0)->Accept(mv);
  this->GetInput(0)->BuildCellLinks();
  m_Centers = simplexVisitor->GetCenterMap();
}

template <typename TInputMesh, typename TOutputMesh>
void
SimplexMeshToTriangleMeshFilter<TInputMesh, TOutputMesh>::CreateTriangles()
{
  auto meshSource = AutoMeshSourceType::New();

  const typename TInputMesh::ConstPointer           inputMesh = this->GetInput(0);
  const typename InputPointsContainer::ConstPointer points = inputMesh->GetPoints();
  typename TInputMesh::PointsContainerConstIterator pointsIt = points->Begin();

  meshSource->Update();

  while (pointsIt != points->End())
  {
    typename InputMeshType::IndexArray n = this->GetInput(0)->GetNeighbors(pointsIt.Index());

    const CellIdentifier newId1 = FindCellId(n[0], pointsIt.Index(), n[1]);
    const CellIdentifier newId2 = FindCellId(n[1], pointsIt.Index(), n[2]);
    const CellIdentifier newId3 = FindCellId(n[2], pointsIt.Index(), n[0]);

    typename AutoMeshSourceType::PointType p1;
    const bool                             b1 = m_Centers->GetElementIfIndexExists(newId1, &p1);
    typename AutoMeshSourceType::PointType p2;
    const bool                             b2 = m_Centers->GetElementIfIndexExists(newId2, &p2);
    typename AutoMeshSourceType::PointType p3;
    const bool                             b3 = m_Centers->GetElementIfIndexExists(newId3, &p3);

    meshSource->AddTriangle(p1, p2, p3);

    if (!(b1 && b2 && b3))
    {
      itkExceptionStringMacro("Assertion failed for test of GetElementIfIndexExists()");
    }

    ++pointsIt;
  }

  this->ProcessObject::SetNthOutput(0, meshSource->GetOutput());
}

template <typename TInputMesh, typename TOutputMesh>
auto
SimplexMeshToTriangleMeshFilter<TInputMesh, TOutputMesh>::FindCellId(CellIdentifier id1,
                                                                     CellIdentifier id2,
                                                                     CellIdentifier id3) -> CellIdentifier
{
  std::set<CellIdentifier> cells1 = this->GetInput(0)->GetCellLinks()->GetElement(id1);
  std::set<CellIdentifier> cells2 = this->GetInput(0)->GetCellLinks()->GetElement(id2);
  std::set<CellIdentifier> cells3 = this->GetInput(0)->GetCellLinks()->GetElement(id3);
  auto                     cellIt = cells1.begin();

  while (cellIt != cells1.end())
  {
    auto found2 = std::find(cells2.begin(), cells2.end(), *cellIt);
    auto found3 = std::find(cells3.begin(), cells3.end(), *cellIt);

    if (found2 != cells2.end() && found3 != cells3.end())
    {
      break;
    }
    ++cellIt;
  }

  if (cellIt == cells1.end())
  {
    itkExceptionStringMacro("Cell was not found, although it should be there");
  }

  return *cellIt;
}

template <typename TInputMesh, typename TOutputMesh>
void
SimplexMeshToTriangleMeshFilter<TInputMesh, TOutputMesh>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  itkPrintSelfObjectMacro(Centers);
}
} // namespace itk

#endif //__SimplexMeshToTriangleMeshFilter_hxx
